package com.bc.bccore.web3j;

import com.bc.bccore.webconfig.WebConfig;
import com.bc.commoncore.security.AES;
import com.bc.commoncore.util.Digest;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.*;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.DefaultBlockParameterNumber;
import org.web3j.protocol.core.methods.response.Transaction;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.*;

import static java.util.stream.Collectors.toList;


/**
 * web3j 与 geth进行通讯
 * xujian
 */
public class WalletService {

    private static WalletService walletService = new WalletService();
    private static Web3j web3j;

    static {
        web3j = Web3j.build(new HttpService(WebConfig.getGethUrl()));
    }

    public static WalletService getInstance() {
        return walletService;
    }

    //获取所连接的节点的版本信息
    public String getClientVersion() throws IOException {
        return web3j.web3ClientVersion().send().getWeb3ClientVersion();
    }

    //得到当前块高
    public BigInteger blockNumber() throws IOException {
        return web3j.ethBlockNumber().send().getBlockNumber();
    }

    //发送交易广播
    private String sendRawTransaction(String signedTransactionData) throws IOException {
        return web3j.ethSendRawTransaction(signedTransactionData).send().getTransactionHash();
    }

    public String resendContractTransaction(String pk,
                                            String contractAddress,
                                            String to,
                                            BigInteger gasPrice,
                                            BigInteger gasLimit,
                                            BigInteger amount,
                                            BigInteger nonce) throws IOException {
        Function function = new Function("transfer",
                Arrays.asList(new Address(to), new Uint256(amount)),
                Collections.emptyList());
        String data = FunctionEncoder.encode(function);

        Credentials credentials = Credentials.create(pk);
        RawTransaction rawTx = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, contractAddress, data);
        byte[] signedMessage = TransactionEncoder.signMessage(rawTx, credentials);
        String signedTransactionData = Numeric.toHexString(signedMessage);

        return sendRawTransaction(signedTransactionData);
    }
    //合约交易
    public String sendContractTransaction(String pk,
                                          String contractAddress,
                                          String to,
                                          BigInteger gasPrice,
                                          BigInteger gasLimit,
                                          BigInteger amount) throws IOException {
        Function function = new Function("transfer",
                Arrays.asList(new Address(to), new Uint256(amount)),
                Collections.emptyList());
        String data = FunctionEncoder.encode(function);

        Credentials credentials = Credentials.create(pk);
        BigInteger nonce = getNonce(credentials.getAddress());
        RawTransaction rawTx = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, contractAddress, data);
        byte[] signedMessage = TransactionEncoder.signMessage(rawTx, credentials);
        String signedTransactionData = Numeric.toHexString(signedMessage);

        return sendRawTransaction(signedTransactionData);
    }

    public String resendEthTranscation(String pk,
                                       String to,
                                       BigInteger value,
                                       BigInteger gasPrice,
                                       BigInteger gasLimit,
                                       BigInteger nonce) throws IOException {
        Credentials credentials = Credentials.create(pk);
        String data = "";
        RawTransaction rawTx = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, value, data);
        byte[] signedMessage = TransactionEncoder.signMessage(rawTx, credentials);
        String signedTransactionData = Numeric.toHexString(signedMessage);
        return sendRawTransaction(signedTransactionData);
    }


    //ETH交易
    public String sendETHTransaction(String pk,
                                     String to,
                                     BigInteger value,
                                     BigInteger gasPrice,
                                     BigInteger gasLimit) throws IOException {
        Credentials credentials = Credentials.create(pk);
        BigInteger nonce = getNonce(credentials.getAddress());
        String data = "";
        RawTransaction rawTx = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, value, data);
        byte[] signedMessage = TransactionEncoder.signMessage(rawTx, credentials);
        String signedTransactionData = Numeric.toHexString(signedMessage);

        return sendRawTransaction(signedTransactionData);
    }

    //获取账户最新eth余额
    public BigDecimal getETHAccountBalance(String address) throws IOException {
        BigInteger balance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send().getBalance();
        return Convert.fromWei(balance.toString(), Convert.Unit.ETHER);
    }

    //获取地址交易次数
    public BigInteger getTransactionCount(String address) throws IOException {
        return web3j.ethGetTransactionCount(address, DefaultBlockParameterName.LATEST).send().getTransactionCount();
    }

    //获取gasPrice
    public BigInteger gasPrice() throws IOException {
        return web3j.ethGasPrice().send().getGasPrice();
    }

    //获取地址code
    public String getCode(String address) throws IOException {
        return web3j.ethGetCode(address, DefaultBlockParameterName.LATEST).send().getCode();
    }

    //获取指定块交易hash列表
    public List<String> getBlockTransactions(BigInteger blockNumber) throws IOException {
        EthBlock.Block block = web3j.ethGetBlockByNumber(new DefaultBlockParameterNumber(blockNumber), false).send().getBlock();
        return block.getTransactions().stream()
                .map(EthBlock.TransactionResult::get)
                .map(x -> (String) x)
                .collect(toList());
    }

    //获取指定id交易
    public Transaction getTransactionByHash(String transactionHash) throws IOException {
        return web3j.ethGetTransactionByHash(transactionHash)
                .send()
                .getTransaction()
                .orElse(null);
    }

    //获取交易收据，需要周期性的检查交易数据
    public TransactionReceipt getTransactionReceipt(String transactionHash) throws IOException {
        return web3j.ethGetTransactionReceipt(transactionHash)
                .send()
                .getTransactionReceipt()
                .orElse(null);
    }

    //生成钱包地址
    public Map<String, String> createNewWallet() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException {
        ECKeyPair keyPair = Keys.createEcKeyPair();
        String privateKey = keyPair.getPrivateKey().toString(16);
        String publicKey = keyPair.getPublicKey().toString(16);
        String account = Keys.getAddress(keyPair);
        Map<String, String> walletKeys = new HashMap<>();
        walletKeys.put("privateKey", privateKey);
        walletKeys.put("publicKey", publicKey);
        walletKeys.put("account", account);
        return walletKeys;
    }

    // get the next available nonce
    private BigInteger getNonce(String account) throws IOException {
        return web3j.ethGetTransactionCount(account, DefaultBlockParameterName.LATEST).send().getTransactionCount();
    }

    public String encodePK(String pk, String str) {
        String pw = Digest.signMD5(str, Digest.ENCODE).substring(1,17);
        return AES.aesEncrypt(pw, pk);
    }

    public String decodePK(String spk, String str) {
        String pw = Digest.signMD5(str, Digest.ENCODE).substring(1,17);
        return AES.aesDecrypt(pw, spk);
    }
}
